﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
/// SSRS
using Microsoft.ReportingServices.DataProcessing;
/// PowerShell
using System.Management.Automation;
using System.Management.Automation.Runspaces;


namespace PowerShellDataProcessingExtension
{
    /// <summary>
    /// Provides a method of reading a forward-only, read-only stream of data from your data source.
    /// </summary>
    /// <remarks>
    /// The IDataReader interface enables you to implement a DataReader class, which provides a means of reading one or more 
    /// forward-only streams of result sets. For more information about DataReader classes, see Implementing a DataReader Class 
    /// for a Data Processing Extension.  An application does not create an instance of the IDataReader interface directly, 
    /// but creates an instance of a class that implements IDataReader.  Classes that implement IDataReader must also implement 
    /// the required members, and typically define additional members to add provider-specific functionality.
    /// Changes made to a result set by another process or thread while data is being read may be visible to the user of a 
    /// class that implements an IDataReader. However, the precise behavior is both provider and timing dependent.
    /// Users do not create an instance of a DataReader class directly. Instead, they obtain the DataReader through the 
    /// ExecuteReader method of the Command object. Therefore, you should mark DataReader constructors as internal.
    /// </remarks>
    class PSDataReader : IDataReader
    {
        private PSDBConnectionExtension connection;
        //private TraceSourceEx trace;
        internal IEnumerator<PSObject> psObjectEnumerator;
        internal PSDataCollection<PSObject> psObjectOutput;
        internal Int16 fieldCount = 0;
        internal int currentPSObject = -1;
        internal List<String> propertyNames = new List<String>();
        internal List<Type> propertyTypes = new List<Type>();

        internal IAsyncResult async;
        internal PowerShell powershell;

        /// TraceSource for the Activity
        private static TraceSourceEx trace = TraceSourceEx.Instance;

        /// <summary>
        /// Gets the number of fields in the data reader
        /// </summary>
        public int FieldCount
        {
            get
            {
                return this.propertyNames.Count;
            }
        }

        /// <summary>
        /// Gets the Type information corresponding to the type of Object that would be returned from GetValue
        /// </summary>
        /// <param name="fieldIndex"></param>
        /// <returns></returns>
        public Type GetFieldType(int fieldIndex)
        {
            return this.propertyTypes[fieldIndex];
        }

        /// <summary>
        /// Gets the name of the field with the given index.
        /// </summary>
        /// <param name="fieldIndex"></param>
        /// <returns></returns>
        public String GetName(int fieldIndex)
        {
            return this.propertyNames[fieldIndex];
        }

        /// <summary>
        /// Return the index of the named field.
        /// </summary>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public int GetOrdinal(string fieldName)
        {
            return this.propertyNames.IndexOf(fieldName);
        }

        /// <summary>
        /// Return the value of the specified field.
        /// </summary>
        /// <param name="fieldIndex"></param>
        /// <returns></returns>
        public Object GetValue(int fieldIndex)
        {
            object value;
            try
            {
                value = this.psObjectOutput[this.currentPSObject].Properties[this.propertyNames[fieldIndex]].Value;
            }
            catch (System.NullReferenceException)
            {
                value = "";
            }
            catch (Exception ex)
            {
                value = ex.InnerException;
            }
            return value;
        }

        /// <summary>
        /// Advances the IDataReader to the next record.
        /// </summary>
        /// <returns></returns>
        public Boolean Read()
        {

            if (this.psObjectEnumerator != null)
            {
                Boolean notEOF = this.psObjectEnumerator.MoveNext();
                if (notEOF)
                {
                    this.currentPSObject++;

                    PSObject psObject = this.psObjectOutput[this.currentPSObject];
                    foreach (PSMemberInfo property in psObject.Properties)
                    {
                        switch (property.MemberType)
                        {
                            case PSMemberTypes.AliasProperty:
                            case PSMemberTypes.NoteProperty:
                            case PSMemberTypes.Properties:
                            case PSMemberTypes.Property:
                            case PSMemberTypes.CodeProperty:
                                {
                                    this.propertyNames.Add(property.Name);

                                    ///
                                    /// Figure out the .NET type of the value
                                    /// 
                                    Type type = Type.GetType(property.TypeNameOfValue);
                                    this.propertyTypes.Add(type);
                                    break;
                                }
                            ///
                            /// TODO: update the DPE to handle PowerShell Script Properties
                            /// 
                            //case PSMemberTypes.ScriptProperty:
                            //        {
                            //            Pipeline p = this.powershell.Runspace.CreatePipeline(property.Value.ToString());
                            //            Collection<PSObject> result = p.Invoke();
                            //            if (result.Count > 0)
                            //            {
                            //                string value = result[0].ToString();
                            //            }


                            //            break;
                            //        }
                            default:
                                {
                                    trace.TraceWarning("The PowerShell DPE does not currently support this PSMemberType: ", property.MemberType.ToString());
                                    break;
                                }

                        }
                    }
                }
                return notEOF;
            }
            this.powershell.EndInvoke(this.async);
            return false;
        }

        public void Dispose() { }

        /// <summary>
        /// Runs the PowerShell Script asynchronously   
        /// </summary>
        public void RunScript(String script, PSDataParameterCollection psDataParameterCollection)
        {
            trace.TraceStart("START PowerShellDataProcessingExtension RunScript");

            //Creating a PSobject to represent the psDataParameterCollection            
            PSObject psObject = new PSObject();
            foreach (PSDataParameter psDataParameter in psDataParameterCollection)
            {
                psObject.Properties.Add(new PSNoteProperty(psDataParameter.ParameterName, psDataParameter.Value));
            }

            /// Add the PSDataParameterCollection as a variable in the InitialSessionState
            InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
            initialSessionState.Variables.Add(new SessionStateVariableEntry("psDataParameterCollection", psDataParameterCollection, null));
            initialSessionState.Variables.Add(new SessionStateVariableEntry("psDataParameters", psObject, null));

            // Call the RunspaceFactory.CreateRunspace() method to create the runspace where the pipeline is run.
            Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState);
            runspace.Open();

            this.powershell = PowerShell.Create();

            powershell.Runspace = runspace;
            powershell.AddScript(script);
            powershell.AddParameter("Verbose");

            // Invoke the PowerShell Pipeline asynchronously
            trace.TraceInformation("### Invoking Pipeline\n\n###PowerShell Script from SSRS\n{0}", script);
            try
            {
                this.psObjectOutput = new PSDataCollection<PSObject>();
                PSDataCollection<PSObject> input = new PSDataCollection<PSObject>();
                input.Complete();

                this.async = powershell.BeginInvoke(input, this.psObjectOutput);

                this.psObjectEnumerator = this.psObjectOutput.GetEnumerator();

                // Throw on any non-terminating errors.
                foreach (ErrorRecord error in powershell.Streams.Error)
                {
                    trace.TraceWarning("PowerShell Error: {0}", error);
                    String errorText = String.Format("PowerShell DPE failed with the following error: \n{0}", error.ToString());
                    throw new Exception(String.Format("PowerShell DPE\n{0}", errorText));
                }

            }
            catch (RuntimeException ex)
            {
                trace.TraceError("PowerShell Error: {0}", ex.Message);
                throw;
            }
            finally
            {
                // Throw on any non-terminating errors.
                String verboseOutput = "PowerShell Verbose Output:\n";
                foreach (VerboseRecord verboseRecord in powershell.Streams.Verbose)
                {
                    verboseOutput += verboseRecord.ToString();
                }
                trace.TraceVerbose(verboseOutput);
            }

            /// 
            /// Peek ahead to populate the type and name information for the fields, then reset the enumerator and our internal counter
            /// per: http://msdn.microsoft.com/en-us/library/ms153655.aspx
            /// The DataReader object returned by the ExecuteReader method needs to contain type and name information for the fields, or columns, in your result set.
            this.Read();
            this.psObjectEnumerator.Reset();
            this.currentPSObject = -1;

            trace.TraceStop("STOP PowerShellDataProcessingExtension RunScript");
        }
    }
}
